/*
 * Adam Blackwell ada2358@ccs.neu.edu
 */
/**
 * Map :
 * 
 * @author Adam
 * 
 */
public abstract class Map {
  // Default Constructor.
  public static Map empty(int h, int w) {
    return new EmptyMap(h, w);
  }
  
  // Constructor.
  public static Map artifact(int h, int w, Artifact a) {
    return new SingleArtifact(h, w, a);
  }
  
  // Creates a map with two maps merged.
  public static Map merge(Map m1, Map m2) {
    return new MergeMap(m1, m2);
  }
  
  // Gives the size of this map as a Coord.
  public abstract Coord size();
  
  // Gives whether or not this map is empty.
  public abstract boolean isEmpty();
  
  // Gives whether or not this map contains the given string.
  protected abstract boolean containsArtifact(String s);
  
  // Gives the first artifact in this map.
  public abstract Artifact firstArtifact();
  
  // Gives a map of the rest of the artifacts in this map.
  public abstract Map restArtifacts();
  
  // Gives a map where the specified artifact has been moved (dx, dy).
  public abstract Map moveArtifact(String s, int h, int w);
  
  // Gives a stringified version of this map.
  public abstract String toString();
  
  // Gives the hashCode for this map.
  public abstract int hashCode();
  
  // Gives whether or not this map equals the given map.
  public abstract boolean equals(Map m);
  
  // Gives whether or not this map equals an object that's not a map.
  public boolean equals(Object obj) {
    return false;
  }
  
  // Empty Map : Inner Class.
  private static class EmptyMap extends Map {
    // Fields.
    private int height;
    private int width;
    
    public EmptyMap(int h, int w) {
      if (h < 0 || w < 0) {
        throw new IllegalArgumentException("Width and Height must be positive.");
      }
      height = h;
      width = w;
    }
    
    public boolean equals(Map m) {
      return m.size().equals(Coord.create(this.width, this.height));
    }
    
    public Artifact firstArtifact() {
      throw new IllegalArgumentException(
                                         "Trying to get the first artifact of an empty list.");
    }
    
    public int hashCode() {
      return 0;
    }
    
    public Map moveArtifact(String s, int h, int w) {
      throw new IllegalArgumentException(
                                         "Trying to move an artifact in an empty list.");
    }
    
    public Map restArtifacts() {
      throw new IllegalArgumentException(
                                         "Trying to call the rest of the artifacts from an empty list.");
    }
    
    public String toString() {
      return "";
    }
    
    public boolean containsArtifact(String s) {
      return false;
    }
    
    public boolean isEmpty() {
      return true;
    }
    
    public Coord size() {
      return Coord.create(this.height, this.width);
    }
  }
  
  // Single Artifact : Inner Class.
  private static class SingleArtifact extends Map {
    // Fields.
    private Artifact artifact;
    private int      height;
    private int      width;
    
    public SingleArtifact(int h, int w, Artifact a) {
      if ((a.position().xPos() < 0 || a.position().xPos() > w)
          || (a.position().yPos() < 0 || a.position().yPos() > h)) {
        throw new IllegalArgumentException("Artifact must be within the map.");
      }
      height = h;
      width = w;
      artifact = a;
    }
    
    public boolean equals(Map m) {
      return !m.isEmpty() && m.firstArtifact().equals(this.artifact)
             && m.restArtifacts().isEmpty()
             && m.size().equals(Coord.create(this.height, this.width));
    }
    
    public Artifact firstArtifact() {
      return this.artifact;
    }
    
    public int hashCode() {
      return this.artifact.hashCode();
    }
    
    public Map moveArtifact(String s, int h, int w) {
      if (this.artifact.name() != s) {
        throw new IllegalArgumentException("Trying to move an unreferenced artifact.");
      }
      return new SingleArtifact(this.height, this.width, this.artifact.move(h,
                                                                            w));
    }
    
    public Map restArtifacts() {
      return new EmptyMap(this.height, this.width);
    }
    
    public String toString() {
      return this.artifact.toString();
    }
    
    public boolean containsArtifact(String s) {
      return this.artifact.name() == s;
    }
    
    public boolean isEmpty() {
      return false;
    }
    
    public Coord size() {
      return Coord.create(this.height, this.width);
    }
  }
  
  // Merge Map : Inner Class.
  private static class MergeMap extends Map {
    // Fileds.
    private Map m1;
    private Map m2;
    
    public MergeMap(Map x, Map y) {
      if (x.size().xPos() != y.size().xPos()
          || x.size().yPos() != y.size().yPos()) {
        throw new IllegalArgumentException("Trying to merge two maps without the same dimensions.");
      }
      m1 = x;
      m2 = y;
    }
    
    public boolean equals(Map m) {
      boolean result;
      if (m.isEmpty() && m1.isEmpty() && m2.isEmpty()
          && m.size().equals(m1.size())) {
        result = true;
      } else if (!m.isEmpty() && m1.isEmpty()
                 && m.firstArtifact().equals(m2.firstArtifact())
                 && m.restArtifacts().equals(m2.restArtifacts())
                 && m.size().equals(m1.size())) {
        result = true;
      } else if (!m.isEmpty() && !m1.isEmpty()
                 && m.firstArtifact().equals(m1.firstArtifact())
                 && m.restArtifacts().equals(merge(m1.restArtifacts(), m2))
                 && m.size().equals(m1.size())) {
        result = true;
      } else {
        result = false;
      }
      return result;
    }
    
    public Artifact firstArtifact() {
      Artifact result;
      if (!this.m1.isEmpty()) {
        result = this.m1.firstArtifact();
      } else {
        result = this.m2.firstArtifact();
      }
      return result;
    }
    
    public int hashCode() {
      return this.m1.hashCode() + this.m2.hashCode();
    }
    
    public Map moveArtifact(String s, int h, int w) {
      Map result;
      if (this.m1.containsArtifact(s)) {
        result = merge(m1.moveArtifact(s, h, w), m2);
      } else if (this.m2.containsArtifact(s)) {
        result = merge(m1, m2.moveArtifact(s, h, w));
      } else {
        throw new IllegalArgumentException("Trying to move an uninitialized artifact.");
      }
      return result;
    }
    
    public Map restArtifacts() {
      Map result;
      if (m1.isEmpty()) {
        result = m2.restArtifacts();
      } else {
        result = Map.merge(m1.restArtifacts(), m2);
      }
      return result;
    }
    
    public String toString() {
      return m1.toString() + " " + m2.toString();
    }
    
    public boolean containsArtifact(String s) {
      return this.m1.containsArtifact(s) || this.m2.containsArtifact(s);
    }
    
    public boolean isEmpty() {
      return false;
    }
    
    public Coord size() {
      return this.m1.size();
    }
  }
}
